package openfalcon

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"path"
	"regexp"
	"strings"

	"golang.org/x/net/context/ctxhttp"

	"github.com/grafana/grafana/pkg/components/null"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/tsdb"
	"github.com/opentracing/opentracing-go"
)

type OpenFalconExecutor struct {
	HttpClient *http.Client
}

func NewOpenFalconExecutor(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
	return &OpenFalconExecutor{}, nil
}

var glog = log.New("tsdb.openfalcon")

func init() {
	tsdb.RegisterTsdbQueryEndpoint("fastweb-openfalcon-datasource", NewOpenFalconExecutor)
}

func (e *OpenFalconExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
	result := &tsdb.Response{}

	gFrom, err := DateParse("-"+tsdbQuery.TimeRange.From, false)
	if err != nil {
		return nil, err
	}
	gUntil, err := DateParse(tsdbQuery.TimeRange.To, true)
	if err != nil {
		return nil, err
	}
	from := gFrom.ToUnix()
	until := gUntil.ToUnix()
	if setting.Env == setting.DEV {
		glog.Debug("52", "From", tsdbQuery.TimeRange.From, "To", tsdbQuery.TimeRange.To, "from", from, "until", until)
	}

	var target string

	formData := url.Values{
		"from":          []string{fmt.Sprint(from)},  //string{from},
		"until":         []string{fmt.Sprint(until)}, //string{until},
		"format":        []string{"json"},
		"maxDataPoints": []string{"500"},
	}

	for _, query := range tsdbQuery.Queries {
		glog.Info("Open-Falcon", "query", query.Model)
		fullTarget, err := query.Model.Get("targetFull").String()
		if err == nil {
			target = fullTarget
		} else {
			target = query.Model.Get("target").MustString()
		}
		target = fixTarget(target)
		if setting.Env == setting.DEV {
			glog.Debug("74", "target", target, "fullTarget", fullTarget)
		}
	}

	formData["target"] = []string{target}

	if setting.Env == setting.DEV {
		glog.Debug("Open-Falcon request", "params", formData)
	}

	req, err := e.createRequest(dsInfo, formData)
	if err != nil {
		return nil, err
	}

	httpClient, err := dsInfo.GetHttpClient()
	if err != nil {
		return nil, err
	}

	span, ctx := opentracing.StartSpanFromContext(ctx, "openfalcon query")
	span.SetTag("target", target)
	span.SetTag("from", from)
	span.SetTag("until", until)
	span.SetTag("datasource_id", dsInfo.Id)
	span.SetTag("org_id", dsInfo.OrgId)

	defer span.Finish()

	opentracing.GlobalTracer().Inject(
		span.Context(),
		opentracing.HTTPHeaders,
		opentracing.HTTPHeadersCarrier(req.Header))

	res, err := ctxhttp.Do(ctx, httpClient, req)
	if err != nil {
		return nil, err
	}

	data, err := e.parseResponse(res)
	if err != nil {
		return nil, err
	}

	result.Results = make(map[string]*tsdb.QueryResult)
	queryRes := tsdb.NewQueryResult()

	for _, series := range data {
		queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
			Name:   series.Target,
			Points: series.DataPoints,
		})

		if setting.Env == setting.DEV {
			glog.Debug("Open-Falcon response", "target", series.Target, "datapoints", len(series.DataPoints))
		}
	}

	result.Results["A"] = queryRes
	return result, nil
}

func (e *OpenFalconExecutor) parseResponse(res *http.Response) ([]TargetResponseDTO, error) {
	body, err := ioutil.ReadAll(res.Body)
	defer res.Body.Close()
	if err != nil {
		return nil, err
	}

	if setting.Env == setting.DEV {
		glog.Debug("144", "body", string(body))
	}

	if res.StatusCode/100 != 2 {
		glog.Info("Request failed", "status", res.Status, "body", string(body))
		return nil, fmt.Errorf("Request failed status: %v", res.Status)
	}

	var data []TargetResponseDTO
	var openFalconData []OpenFalconD

	err = json.Unmarshal(body, &openFalconData)
	if setting.Env == setting.DEV {
		glog.Debug("157", "openFalconData", openFalconData)
	}

	if err != nil {
		glog.Info("Failed to unmarshal Open-Falcon response", "error", err, "status", res.Status, "body", string(body))
		return nil, err
	}

	data = convertDataPoints(openFalconData)
	if setting.Env == setting.DEV {
		glog.Debug("167", "data", data)
	}
	return data, nil
}

func (e *OpenFalconExecutor) createRequest(dsInfo *models.DataSource, data url.Values) (*http.Request, error) {
	u, _ := url.Parse(dsInfo.Url)
	u.Path = path.Join(u.Path, "render")

	req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(data.Encode()))
	if err != nil {
		glog.Info("Failed to create request", "error", err)
		return nil, fmt.Errorf("Failed to create request. error: %v", err)
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	if dsInfo.BasicAuth {
		req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.BasicAuthPassword)
	}

	return req, err
}

func fixTarget(target string) string {
	rTarget := regexp.MustCompile(`\.`)
	return rTarget.ReplaceAllString(target, "#")
}

func convertDataPoints(openFalconData []OpenFalconD) (data []TargetResponseDTO) {
	for i := 0; i < len(openFalconData); i++ {
		d := openFalconData[i]

		var dto TargetResponseDTO
		dto.Target = d.EndPoint + "." + d.Counter

		for j := 0; j < len(d.Values); j++ {
			v := d.Values[j]

			var dataPoint [2]null.Float
			dataPoint[0] = v.Value
			dataPoint[1] = null.FloatFrom(v.Timestamp * 1000)
			dto.DataPoints = append(dto.DataPoints, dataPoint)
		}

		data = append(data, dto)
	}

	return
}
